#!/usr/bin/env python3
# utils/frame_builder.py
# 通信帧工具：构建、解析、接收
import socket
import struct
import zlib
import binascii
import time
import logging

from config import FRAME_SIZE, VERSION

logger = logging.getLogger(__name__)


def build_frame(command, payload=None, robot_id=0):
    """构建32字节帧"""
    if payload is None:
        payload = bytes(14)
    elif len(payload) > 14:
        payload = payload[:14]
    elif len(payload) < 14:
        payload = payload.ljust(14, b"\x00")
    timestamp = int(time.time() * 1000)
    header = struct.pack(">BBiQ", VERSION, command, robot_id, timestamp)
    frame_data = header + payload
    crc32 = zlib.crc32(frame_data) & 0xFFFFFFFF
    crc_bytes = struct.pack(">I", crc32)
    full_frame = frame_data + crc_bytes
    if logger.isEnabledFor(logging.DEBUG):
        logger.debug(f"构建帧: CMD={command}, TS={timestamp}, CRC={crc_bytes.hex()}")
    return full_frame


def parse_frame(data):
    """解析32字节帧"""
    if len(data) != FRAME_SIZE:
        logger.error(f"无效帧长度: {len(data)} (预期 {FRAME_SIZE})")
        return None
    frame_data = data[:28]
    received_crc = struct.unpack(">I", data[28:32])[0]
    calculated_crc = zlib.crc32(frame_data) & 0xFFFFFFFF
    if received_crc != calculated_crc:
        logger.warning(f"CRC 失败: 接收 {received_crc:08X}, 计算 {calculated_crc:08X}")
        return None
    try:
        header_part = frame_data[:14]
        version, command, robot_id, timestamp = struct.unpack(">BBiQ", header_part)
        payload = frame_data[14:28]
        if version != VERSION:
            logger.warning(f"版本不匹配: {version} (预期 {VERSION})")
            return None
        return {
            "version": version,
            "command": command,
            "robot_id": robot_id,
            "timestamp": timestamp,
            "payload": payload,
        }
    except struct.error as e:
        logger.error(f"帧解析错误: {e}")
        return None


def recv_exact(client_socket, size):
    """接收指定长度数据"""
    data = b""
    start_time = time.time()
    while len(data) < size:
        try:
            client_socket.settimeout(1.0)
            chunk = client_socket.recv(size - len(data))
            if not chunk:
                return None
            data += chunk
        except socket.timeout:
            if time.time() - start_time > 10:
                logger.warning("接收超时超过10秒")
                return None
            continue
        except BlockingIOError:
            if time.time() - start_time > 8:
                return None
            time.sleep(0.1)
    return data
